#include "widget.h"
#include "ui_widget.h"
#include <cstring>
#include <iostream>

#ifdef Q_OS_WIN
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>
#endif




Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) {
  ui->setupUi(this);
  this->setWindowTitle("TCP服务端");

  /**
   *  @brief  创建套接字（socket），该套接字可以用于在不同主机之间进行数据传输。
   *  @param  domain：指定协议族（protocol family），即使用的网络协议类型。
   *          常见的协议族有 AF_INET（IPv4）、AF_INET6（IPv6）等。
   *  @param  type：指定套接字类型，表示通信的方式。常见的套接字类型有
   *          SOCK_STREAM（流式套接字，如TCP）和 SOCK_DGRAM（数据报套接字，如UDP）。
   *  @param  protocol：指定具体的协议。通常情况下，可以将其设置为0，
   *          让系统自动选择适合的协议。
   *  @return 函数返回值：如果函数调用成功，将返回一个非负整数，表示套接字的文件描述符。
   *  int socket(int domain, int type, int protocol);
   */
  m_tcpServer = socket(AF_INET, SOCK_STREAM, 0);
  if (m_tcpServer == -1) {
    return;
  }

  connect(&m_timer, &QTimer::timeout, this, &Widget::onReadyRead);
  m_timer.setInterval(100);
#ifdef Q_OS_WIN
  WSADATA wsaData;
  if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
    qDebug() << "Failed to initialize winsock" ;
  }
#endif
}

Widget::~Widget() {
  delete ui;

#ifdef Q_OS_WIN
  WSACleanup();
#endif
}

void Widget::on_btnStart_clicked() {
  if (!m_start) {
    QString ip = ui->lineEditIP->text();
    uint port = ui->lineEditPort->text().toUInt();
    // 设置服务器地址和端口
    struct sockaddr_in serverAddr;
    memset(&serverAddr, 0x00, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(port); // 指定端口
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    inet_pton(AF_INET, ip.toStdString().c_str(), &(serverAddr.sin_addr));

   /**
    * @brief 绑定地址和端口
    * @param sockfd：要绑定的套接字文件描述符。
    * @param  addr：指向要绑定的本地地址的结构体指针。
    * 通常使用struct sockaddr_in结构体表示IPv4地址，
    * 或者struct sockaddr_in6结构体表示IPv6地址。
    * 根据套接字的协议类型不同，可以进行相应的地址转换操作。
    * @param addrlen：本地地址的长度，以字节为单位。
    * @return 如果函数调用成功，返回值为0。如果返回-1，表示绑定失败，
    *  可以通过 errno 变量获取错误代码。
    *  int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    */

    if (bind(m_tcpServer, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) ==
        -1) {
      ::close(m_tcpServer);
      ui->messageShoxEdit->appendPlainText("服务器启动失败！");
      return;
    }

    // 开始监听连接请求，等待客户端来连接

   /**
    * @breif 将一个套接字（socket）转换为监听状态的函数
    * sockfd：一个已经创建并绑定到指定地址的套接字描述符。
    * backlog：定义了服务器能够排队等待的最大连接请求数量。
    */
    int listen(int sockfd, int backlog);

    if (listen(m_tcpServer, 5) == -1) {
      ::close(m_tcpServer);
      ui->messageShoxEdit->appendPlainText("服务器启动失败！");
      return;
    }

    ui->messageShoxEdit->appendPlainText("服务器启动成功！");
    ui->btnStart->setText("停止");
    m_start = true;

    // 将套接字设置为非阻塞模式,否则会阻塞界面
    int flags = fcntl(m_tcpServer, F_GETFL, 0);
    if (flags < 0) {
      perror("fcntl F_GETFL");
      ::close(m_tcpServer);
      return;
    }
    if (fcntl(m_tcpServer, F_SETFL, flags | O_NONBLOCK) < 0) {
      perror("fcntl F_SETFL");
      ::close(m_tcpServer);
      return;
    }

    m_timer.start();

  } else {
    // 关闭与所有客户端的连接
    std::for_each(m_clientList.begin(), m_clientList.end(),
                  [](ClientItem *c) { ::close(c->socket); });
    // 关闭TCP的监听
    ::close(m_tcpServer);
    ui->messageShoxEdit->appendPlainText("服务器已停止!");
    ui->btnStart->setText("启动");
    m_start = false;
  }
}

void Widget::onNewConnection(int socket, const QString &ip, quint32 port) {
  // 将套接字设置为非阻塞模式,否则会阻塞界面
  int flags = fcntl(socket, F_GETFL, 0);

  if (flags < 0) {
    perror("fcntl F_GETFL");
    ::close(socket);
    return;
  }
  if (fcntl(socket, F_SETFL, flags | O_NONBLOCK) < 0) {
    perror("fcntl F_SETFL");
    ::close(socket);
    return;
  }

  // 获取该连接与客户端通讯的套接字对象
  QString info = QString("客户端%1:%2连接成功!").arg(ip).arg(port);
  ui->messageShoxEdit->appendPlainText(info);

  info = QString("%1:%2").arg(ip).arg(port);
  m_clientList.emplace_back(new ClientItem(socket, info));
}

void Widget::onReadyRead() {
  // 不知道是哪个客户端发来的数据
  // 所以需要遍历每个客户端
  struct sockaddr_in clientAddr;
  socklen_t clientAddrLen;
  // 接受客户端连接
  clientAddrLen = sizeof(clientAddr);
  // 每收到一个客户端连接都会创建一个socket。和客户端的数据收发都用新建的socket

  /**
   * @brief 接受客户端的连接请求，创建一个新的套接字并进行通信。
   * @param sockfd：监听套接字的文件描述符，即服务端已经通
   *        过listen()函数监听的套接字。
   * @param addr：指向客户端地址的结构体指针。当accept()
   *        函数成功时，会将客户端的地址信息填充到该结构体中。
   * @param addrlen：客户端地址结构体的长度指针，用于指定addr的大小。
   * @return 如果函数调用成功，返回一个非负整数，表示新创建
   *        的套接字的文件描述符，可以用于与客户端进行通信。
   *         如果返回-1，表示接受连接失败，可以通过 errno 变量
   *         获取错误代码
   */
  int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

  int clientSocket =
      accept(m_tcpServer, (struct sockaddr *)&clientAddr, &clientAddrLen);
  if (clientSocket > 0) {
    char *ip = inet_ntoa(clientAddr.sin_addr);
    onNewConnection(clientSocket, ip, clientAddr.sin_port);
  }

  std::for_each(
      m_clientList.begin(), m_clientList.end(), [this](ClientItem *client) {
        char recvBuffer[512];

        // 接收客户端数据
        memset(recvBuffer, 0, sizeof(recvBuffer));
        ssize_t recvLen =
            recv(client->socket, recvBuffer, sizeof(recvBuffer) - 1, 0);

        // 接收的数据大小
        if (recvLen > 0) {
          QString msg(recvBuffer);
          QString str = QString("收到了%1:消息:%2").arg(client->info).arg(msg);

          ui->messageShoxEdit->appendPlainText(str);
        }
      });

  std::for_each(m_clientList.begin(), m_clientList.end(),
                [this](ClientItem *client) { checkSocket(client); });
}

// 客户端断开的时候发送disconnected的信号的槽函数
void Widget::checkSocket(ClientItem *item) {
  // sender表示发送信号的对象
  int error;
  socklen_t len = sizeof(error);

  bool invalid = false;
  // 检查套接字错误状态
  if (getsockopt(item->socket, SOL_SOCKET, SO_ERROR, &error, &len) != 0) {
    invalid = true;
  }
  if (error != 0) {
    invalid = true;
  }

  if (!invalid) {
    return;
  }

  QString str = QString("客户端%1断开了！").arg(item->info);
  ui->messageShoxEdit->appendPlainText(str);
  ::close(item->socket);
  // 从客户端列表移除
  m_clientList.remove(item);

  delete item;
}

void Widget::on_btnSend_clicked() {
  // 获取要发送的内容
  QString msg = ui->messageEdit->text();
  QByteArray data = msg.toUtf8(); // 转换为utf8字节数组
  // 群发消息
  auto dosend = [this, data](ClientItem *client) {
    send(client->socket, data.toStdString().c_str(), data.size(), 0);
  };
  std::for_each(m_clientList.begin(), m_clientList.end(), dosend);
}
